/**
 * 
 */
package br.org.bertol.mestrado.engine.fitness;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.optimisation.entity.AbstractUnit;
import br.org.bertol.mestrado.engine.optimisation.entity.Path;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoDominace;
import br.org.bertol.mestrado.engine.optimisation.sls.IteratedLocalSearch;

/**
 * @author contaqualquer
 */
public class ClassInstantiation extends AbstractCombinatorialFitness<Integer> {

    /**
     * Conjunto de restrições fortes.
     */
    private final transient List<List<Integer>>                  strongConstraint;

    /***/
    private transient boolean                                    validPath;

    /**
     * Objetivos do problema.
     */
    protected transient Objective[]                              objectives;
    /**
     * Fronteira de Pareto.
     */
    private transient ParetoDominace<EnumMap<Objective, Number>> paretoDominace;

    /**
     * @param sConstraint
     *            Restrições fortes
     */
    public ClassInstantiation(final List<List<Integer>> sConstraint,
        final ArrayList<Integer> classes, final Objective... objectivess) {

        super(classes);

        strongConstraint = sConstraint;

        objectives = objectivess;

    }

    @Override
    public final boolean fitness(final List<Integer> route,
            final EnumMap<Objective, Number> height) {

        verifyStrongConstraint(route, true);

        if (validPath) {

            for (final Objective objective : height.keySet()) {

                height.put(objective, weekConstraint(route, objective));

            }

        }

        return validPath;
    }

    /**
     * @param route
     *            A
     * @param objective
     *            D
     * @return {@link Integer} peso da restrição
     */
    private Integer weekConstraint(final List<Integer> route,
            final Objective objective) {

        // valor da violação
        int peso = 0;

        // verifica todas as dependências para esta solu��o
        // alterada pelo hill climbing
        final Iterator<Integer> iterator = route.iterator();

        for (int x = 0; iterator.hasNext(); x++) {
            final int classe = iterator.next();

            // recupera a lista de restri��es fracas do n� da rota
            // -1 para acertar a posi��o no array
            final List<?> weekConstraints = objective
                    .getParameters()
                    .get(classe - 1);

            // verifica quantas depend�ncias foram violadas
            for (final ListIterator<?> weekconstraint = weekConstraints
                    .listIterator(); weekconstraint.hasNext();) {

                final int value = weekconstraint.nextIndex();

                weekconstraint.next();
                // verifica se essa dependência está na lista
                if (!route.subList(0, x + 1).contains(value) && value > 0) {
                    peso += objective.getConstraintHeight(classe, value);
                }
            }
        }

        return peso;
    }

    /**
     * Retorna o peso de uma determinada classe.
     * @param route
     *            Caminho percorrido até agora
     * @param objective
     *            Objetivo a ser atendido
     * @param classe
     *            Classe a ser pesquisada
     * @return {@link Integer} Peso econtrado
     */
    public final Integer fitness(final List<Integer> route,
            final Objective objective,
            final int classe) {

        // marcador de peso
        int peso = 0;

        // recupera a lista de dependências do não da rota
        // -1 para acertar a posição no array
        final List<?> weekConstraints = objective.getParameters()
                .get(classe - 1);

        // caso não tenha todas as depend�ncias respeitadas
        if (!route.containsAll(weekConstraints)) {
            // verifica quantas depend�ncias foram violadas
            for (final ListIterator<?> weekconstraint = weekConstraints
                    .listIterator(); weekconstraint.hasNext();) {

                final int value = weekconstraint.nextIndex();

                weekconstraint.next();
                // verifica se essa depend�ncia est� na lista
                if (!route.contains(value) && value > 0) {
                    peso += objective.getConstraintHeight(classe, value);
                }
            }
        }

        return peso;

    }

    /**
     * Veririca a lista de precedentes para cada n� do caminho. Caso ocorra
     * alguma viola��o, invalida o caminho.
     * @param route
     *            Unidade a ser avaliada
     * @param fullCheck
     *            se é preciso realizar uma verificação full no caminho
     * @see Path
     */
    public final void verifyStrongConstraint(
            final List<Integer> route,
            final boolean fullCheck) {

        boolean hasPrecedent = true;

        // verifica se � preciso realizar verifica��o full das preced�ncias
        if (fullCheck) {
            // verifica n� a n� se foi quebrada alguma preced�ncia
            final Iterator<Integer> iterator = route.iterator();

            for (int x = 0; iterator.hasNext(); x++) {
                final int classe = iterator.next();

                // recupera a lista de restri��es fortes do n� da rota
                // -1 para acertar a posi��o no array
                final List<Integer> precedentes = strongConstraint
                        .get(classe - 1);

                // verifica se houve quebra de precedencia
                if (!route.subList(0, x + 1).containsAll(precedentes)) {
                    hasPrecedent = false;
                    break;
                }
            }
        } else {
            // pega o último elemento da lista
            final int lastClass = route.get(route.size() - 1);

            // recupera a lista de precedentes do n� da rota
            // -1 para acertar a posição no array
            final List<Integer> precedentes =
                    strongConstraint.get(lastClass - 1);

            if (!route.containsAll(precedentes)) {
                hasPrecedent = false;
            }
        }

        // avalia se referencias fortes foram quebradas
        // caso haja quebra na precedencia, marca o caminho como invi�vel
        // caso contr�rio invalida o caminho e termina o processo
        if (!hasPrecedent) {
            validPath = hasPrecedent;
        } else {
            validPath = hasPrecedent;
        }

    }

    @Override
    public final void reset() {
        validPath = true;

    }

    @Override
    public final boolean stopCriteria() {
        return false;
    }

    /**
     * Aplica busca ILS.
     * @param unit
     *            Partícula a ser melhorada
     * @return Partícula melhorada
     * @throws Exception
     *             Erro
     */
    private <U extends AbstractUnit<Integer>> U applyILS(final U unit)
            throws Exception {

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pré ILS ---------------------------- : "
                + unit);

        final IteratedLocalSearch<Integer, U> ils =
                new IteratedLocalSearch<Integer, U>(
                unit, 10, classes, objectives);

        ils.evaluate();

        final U newUnit = ils.getBetterUnit();

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pós ILS ---------------------------- : "
                + newUnit);

        if (paretoDominace.compare(newUnit.getHeight(), unit
                .getHeight()) == ParetoDominace.LEFT_DOMINE) {
            newUnit.update();
        }

        return newUnit;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final <U extends AbstractUnit<Integer>> U doBetter(final U unit)
            throws Exception {

        try {
            return applyILS(unit);
        } catch (Exception e) {
            Logger.getLogger(getClass()).error("Erro ao melhorar solução.", e);
        }

        return unit;

    }

}
